package com.ruoyi.article.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.article.entity.ArticleBasis;
import com.ruoyi.article.entity.ArticleVo;
import com.ruoyi.article.entity.GMapNetCache;
import com.ruoyi.article.mapper.ArticleBasisMapper;
import com.ruoyi.article.mapper.ArticleMapper;
import com.ruoyi.article.mapper.GmapNetCacheMapper;
import com.ruoyi.article.service.ArticleService;
import com.ruoyi.common.utils.RedisDistributedLock;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.entity.Article;
import com.ruoyi.publisher.RabbitMQProducer;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;


/**
 * @author hlf
 * @createTime 2025/2/11 10:49
 * @description
 */
@Service
@Slf4j
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private RabbitMQProducer rabbitMQProducer;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private RedisDistributedLock redisDistributedLock;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private GmapNetCacheMapper gmapNetCacheMapper;

    @Autowired
    private ArticleBasisMapper articleBasisMapper;


    @Override
    public void add(ArticleVo article) {
        article.setId(IdUtils.fastSimpleUUID());
        Article act =  BeanUtil.copyProperties(article, Article.class);
        act.setSuggestion(new Completion(new String[]{article.getContent(), article.getTitle()}));
        articleMapper.add(article);
        // 走mq
        rabbitMQProducer.sendArticleMessage(JSONUtil.toJsonStr(act));

    }

    @Override
    public void addAll(ArticleVo article) {
        List<ArticleVo> arrayList = articleMapper.getAllArticle();
        List<Article> articles = BeanUtil.copyToList(arrayList, Article.class);
        for (Article art : articles) {
            art.setSuggestion(new Completion(new String[]{art.getContent(), art.getTitle()}));
        }
        // 走mq
        rabbitMQProducer.sendArticleMessageAll(JSONUtil.toJsonStr(articles));
    }

    @Override
    public void updateArticle(ArticleVo article) {
        ArticleBasis articleBasis = new ArticleBasis();
        BeanUtil.copyProperties(article, articleBasis);
        articleBasisMapper.updateArticle(articleBasis);
        // 走mq es新增
        rabbitMQProducer.sendArticleMessage(JSONUtil.toJsonStr(article));
    }

    @Override
    public void deleteArticle(ArticleVo article) {
        articleBasisMapper.deleteArticleById(article.getId());
        // 走mq es删除
        rabbitMQProducer.sendArticleMessageDel(JSONUtil.toJsonStr(article));
    }



    /**
     * @param article
     * @return void
     * @description 多线程使用示例
     * @author hlf
     * @date 2025/2/14 13:47
     **/
    @Override
    public CompletableFuture<Integer> asyncTestExample(ArticleVo article) {
        ThreadPoolTaskExecutor executor = SpringUtils.getBean("threadPoolTaskExecutor");
        // 创建多个异步任务，并通过线程池执行
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Task 1 is running in thread: " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        }, executor);

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("Task 2 is running in thread: " + Thread.currentThread().getName());
            try {
                Thread.sleep(2000); // 模拟一些耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        }, executor);

        // 等待两个任务的完成并进行处理
        CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
            System.out.println("Task 1 and Task 2 completed!");
            return result1 + result2;
        });

        // 返回结果
        return combinedFuture;

    }

    /**
     * @param article
     * @return java.lang.String
     * @description 测试使用分布式锁 手动实现
     * @author hlf
     * @date 2025/2/17 15:30
     **/
    @Override
    public void updateArticlePriceTest(ArticleVo article) {
        String lockKey = "some_resource";
        String requestId = IdUtils.fastSimpleUUID();
        boolean lockAcquired = false;
        try {
            // 尝试获取锁
            lockAcquired = redisDistributedLock.tryLockWithDefaultRetry(lockKey, requestId);
            if (lockAcquired) {
                // 启动看门狗线程，定期续期锁
                redisDistributedLock.startWatchdog(lockKey, requestId);
                // 获取锁成功，执行业务逻辑
                log.info("获取锁成功,当前金额为:{}", article.getPrice());
                Thread.sleep(3000);
                articleMapper.updateArticlePrice(article);
                log.info("修改成功,当前金额为:{}", article.getPrice());
            } else {
                // 获取锁失败
                log.warn("获取锁失败,当前金额为:{}", article.getPrice());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            if (lockAcquired) {
                if (redisDistributedLock.releaseLock(lockKey, requestId)) {
                    log.info("释放锁成功");
                } else {
                    log.warn("释放锁失败");
                }
            }

        }
    }


    /**
     * @param article
     * @return java.lang.String
     * @description 测试使用分布式锁 redission
     * @author hlf
     * @date 2025/2/17 15:30
     * Redisson 内部已经实现了锁的自动续期（看门狗机制）
     **/
/*    RLock lock = redissionClient.getLock(key);
      lock.trylock(waitTime,leaseTime,TimeUnit.SECOND);
    自动续期的前提：自动续期功能要求持有锁的线程必须仍然在运行。如果持有锁的线程意外终止或因其他原因停止运行，那么自动续期将无法执行，锁会在租期到期后被释放。
    Redisson内部机制：Redisson 内部有一个 LockWatchdogTimeout 参数，它控制着自动续期的时间间隔。默认情况下，这个值为 30 秒。
    如果你设置的 leaseTime 比 LockWatchdogTimeout 要长，那么 Redisson 会自动续期。
    可配置性：如果明确设置了 leaseTime，那么在这个时间到期后锁会被释放，不会继续自动续期。如果希望自动续期，可以在调用 tryLock 时将 leaseTime 参数设置为 -1。
    总结一下，如果设置了 leaseTime 为 3600 秒，那么锁会在 3600 秒后释放，除你将 leaseTime 设置为 -1，此时锁会自动续期直到显式释放锁。*/
    @Override
    public void updateArticlePriceTest2(ArticleVo article) {
        String lockKey = "lock:some_resource";
        RLock lock = redissonClient.getLock(lockKey);
        boolean lockAcquired = false;
        try {
            // 尝试获取锁
            lockAcquired = lock.tryLock(0, -1, java.util.concurrent.TimeUnit.SECONDS);
            if (lockAcquired) {
                // 获取锁成功，执行业务逻辑
                log.info("获取锁成功,当前金额为:{}", article.getPrice());
                Thread.sleep(70000); // 模拟业务处理时间
                articleMapper.updateArticlePrice(article);
                log.info("修改成功,当前金额为:{}", article.getPrice());
            } else {
                // 获取锁失败
                log.warn("获取锁失败,当前金额为:{}", article.getPrice());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            if (lockAcquired && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放锁成功");
            } else {
                log.warn("释放锁失败");
            }

        }
    }

    @Override
    public List<ArticleVo> qpsTest(ArticleVo article) {
        return articleMapper.qpsTest(article);
    }

    /**
     * @param article
     * @return void
     * @description 使用分布式锁来模拟库存扣减，避免超卖
     * @author hlf
     * @date 2025/2/19 16:13
     **/
    @Override
    public String cutPrice(ArticleVo article) {
        // id是 264f6323d3184895a059607ada424f2b   价格 只能减5 （模拟5个库存）
        String cacheKey = "article:price:" + article.getId();
        // 从缓存中获取价格
        String priceStr = redisTemplate.opsForValue().get("article:price:" + article.getId());
        // 如果缓存中没有价格，查询数据库并更新缓存
        if (StrUtil.isEmpty(priceStr)) {
            Double price = articleMapper.getById(article).getPrice();
            // 检查价格是否有效（正值），无效则直接返回
            if (price <= 0) {
                log.warn("线程{}:价格为负或零，库存不足", Thread.currentThread().getId());
                redisTemplate.opsForValue().setIfAbsent(cacheKey,"0" , 10 + new Random().nextInt(5), TimeUnit.MINUTES);
                return "库存不足";
            }
            // 使用 setIfAbsent 原子操作，避免缓存击穿
            boolean setSuccess = redisTemplate.opsForValue().setIfAbsent(cacheKey, String.valueOf(price), 10 + new Random().nextInt(5), TimeUnit.MINUTES);
            if (!setSuccess) {
                // 如果 setIfAbsent 失败，说明其他线程已经写入了缓存，重新从缓存中获取
                log.info("写入缓存失败，重新拿缓存中的价格");
                priceStr = redisTemplate.opsForValue().get(cacheKey);
            } else {
                priceStr = String.valueOf(price);
            }
        }
        // 如果缓存中有价格，直接转换为 Double
        Double price = Double.valueOf(priceStr);
        if (price <= 0) {
            // 库存不足，直接返回
            log.warn("线程{}:库存不足", Thread.currentThread().getId());
            return "库存不足";
        }
        RLock lock = redissonClient.getLock("lock:price:" + article.getId());
        try {
            // 尝试获取锁，等待时间为 10 秒，锁的持有时间为 30 秒
            boolean isLocked = lock.tryLock(1, 10, TimeUnit.SECONDS);
            if (!isLocked) {
                // 获取锁失败
                log.warn("线程{}:获取锁失败", Thread.currentThread().getId());
                return "扣减失败,获取锁失败";
            }
            // 再次检查缓存中的价格（防止在获取锁的过程中价格被其他线程修改）
            priceStr = redisTemplate.opsForValue().get(cacheKey);
            price = Double.valueOf(priceStr);
            if (price <= 0) {
                log.warn("线程{}: 库存不足，文章ID: {}", Thread.currentThread().getId(), article.getId());
                return "库存不足";
            }

            // 扣减库存
            articleMapper.updateStockIPrice(article);
            // 更新 Redis 中的价格
            redisTemplate.opsForValue().set(cacheKey, String.valueOf(price - 1), 10, TimeUnit.MINUTES);

            log.info("线程{}:扣减成功", Thread.currentThread().getId());
            return "扣减成功";

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("线程{}: 扣减时被中断", Thread.currentThread().getId(), article.getId(), e);
            return "扣减失败：线程中断";
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.warn("线程{}:释放锁成功", Thread.currentThread().getId());
            }

        }

    }

    @Override
    public GMapNetCache findByTypeAndZoomAndXAndY(int type, int zoom, int x, int y) {
        GMapNetCache gMapNetCache = new GMapNetCache();
        gMapNetCache.setType(type);
        gMapNetCache.setZoom(zoom);
        gMapNetCache.setX(x);
        gMapNetCache.setY(y);
        return gmapNetCacheMapper.getTile(gMapNetCache);
    }



}
